-- vim:ts=4:sw=4:noet
--
--
-- Listeners:
--   chat		= normal chat message (you won't get your own messages)
--   			  f( hub, user, "message" )
--   			  DISCARDABLE:
--   			    return non-nil to hide the msg from rmDC++,
--   			    all discardable functions also get an optional last
--   			    argument stating whether some previous listener returned non-nil
--   ownChat	= normal chat message from yourself (incoming from the hub)
--   			  f( hub, "message" )
--   			  DISCARDABLE
--   ownChatOut	= normal chat message from yourself (outgoing to the hub)
--   			  f( hub, "message" )
--   			  DISCARDABLE
--   pm			= normal pm message
--   			  f( hub, user, "message" )
--   			  DISCARDABLE
--   hubPm		= pm message with a different prefix than the nick in the From field
--   			  f( hub, user, "message which may include a <nickname>" )
--   			  DISCARDABLE
--   connected	= connecting to a hub (many functions may be unavailable (return nil))
--   			  f( hub )
--   disconnected = disconnected from a hub
--   			  f( hub )
--   userConnected = a user connected (or rather.. the first getUser call was made)
--   			  f( hub, user )
--   userMyInfo	= a myinfo message from a user, use this as "userConnected"
--   			  f( hub, user, "$MyINFO $ALL ... 1234$|" )
--   			  DISCARDABLE
--   userQuit	= a quit message from a user
--   			  f( hub, user )
--   			  DISCARDABLE
--   raw		= a full message
--   			  f( hub, raw )
--   			  DISCARDABLE (message won't get parsed.. no other listeners will be called)
--   timer		= called every second, use dcpp:getHubs()[k]:getAddress() to select a hub
--   			  f()
--
-- Example listener (load _after_ this script):
--		dcpp:setListener( "chat", "bier",
--			function( hub, user, text )
--				local s = string.lower( text )
--				if string.find( s, "[^a-z]bier+[^a-z]" ) or string.find( s, "[^a-z]biertje[^a-z]" ) then
--					hub:sendChat( "bier? ja lekker! :)" )
--				end
--			end
--		)
--
-- If you want to remove the "bier" chat listener, simply type:
-- /lua dcpp:setListener( "chat", "bier" )
-- in any chat window


DC():PrintDebug( "** Started startup.lua **" )


--/////////////////////////////////////
--// Hub manager
--/////////////////////////////////////

if not dcpp or dcpp._init_me_anyway == true then
	dcpp = {}
	dcpp._hubs = {}
	dcpp._listeners = {}
end

dcpp.addHub = function( this, hub, isitadc )
	if not this._hubs[hub] then
		--DC():PrintDebug( "Hub added (id = "..tostring( hub )..")" )
		this._hubs[hub] = createHub( hub, isitadc )
		for k,f in dcpp:getListeners( "connected" ) do
			f( this._hubs[hub] )
		end
		return this._hubs[hub]
	else
		--DC():PrintDebug( "Tried to add existing hub on: "..this._hubs[hub]:getHubName()..
		--						" (id = "..tostring( hub )..")" )
		return nil
	end
end

dcpp.getHub = function( this, hub )
	if this._hubs[hub] then
		return this._hubs[hub]
	else
		--DC():PrintDebug( "Tried to fetch an unknown hub (id = "..tostring( hub )..")" )
		return this:addHub( hub ):setPartial() -- tell the object that we missed the logon
	end
end

dcpp.getHubs = function( this )
	return this._hubs
end

dcpp.hasHub = function( this, hub )
	if this._hubs[hub] then
		return 1
	else
		return nil
	end
end

dcpp.removeHub = function( this, hub )
	if this._hubs[hub] then
		--DC():PrintDebug( "Hub removed: "..this._hubs[hub]:getHubName().." (id = "..tostring( hub )..")" )
		for k,f in dcpp:getListeners( "disconnected" ) do
			f( this._hubs[hub] )
		end
		this._hubs[hub]:destroy()
		this._hubs[hub] = nil
	else
		--DC():PrintDebug( "Tried to remove non-existent hub (id = "..tostring( hub )..")" )
	end
end

dcpp.listHubs = function( this )
	DC():PrintDebug( "** Listing hubs **" )
	for k,v in this._hubs do
		DC():PrintDebug( tostring( k ).." ("..v:getHubName()..")" )
	end
end

dcpp.setListener = function( this, ltype, id, func )
	if not dcpp._listeners[ltype] then
		dcpp._listeners[ltype] = {}
	end
	dcpp._listeners[ltype][id] = func
end

dcpp.getListeners = function( this, ltype )
	if dcpp._listeners[ltype] then
		return dcpp._listeners[ltype]
	else
		return {}
	end
end


--/////////////////////////////////////
--// Hub object
--/////////////////////////////////////

function createHub( hubid, isitadc )
	local hub = {}

	hub._id = hubid
	hub._users = {}
	hub._name = nil
	hub._myNick = nil
	hub._partial = nil
	hub._gotOpList = nil
	hub._uptime = os.time()
	hub._isADC = isitadc

	hub.getId = function( this )
		return this._id
	end

	hub.isADC = function( this )
		if this._isADC == 0 then
			return false
		else
			return true
		end
	end

	hub.getUptime = function( this )
		return ( os.time() - hub._uptime ) / 60
	end

	hub.getUser = function( this, nick, op )
		if not this._users[nick] then
			this._users[nick] = createUser( this, nick )
			if this:getUptime() >= 2 then -- start sending messages AFTER logon
	            for k,f in dcpp:getListeners( "userConnected" ) do
					f( this, this._users[nick] )
				end
			end
		end
		local r = this._users[nick]
		if op then r:setOp( 1 ) ; this._gotOpList = 1 end
		return r
	end

	hub.findUsers = function( this, nick, notag )
		-- you get a possibly empty table of users
		if not notag then
			return { this._users[nick] }
		else
			local list = {}
			for k,v in this._users do
				local ret,c,n = string.find( k, "^%[.*%](.-)$" )
				if n == nick then
					table.insert( list, v )
				end
			end
			return list
		end
	end

	hub.gotOpList = function( this )
		return this._gotOpList
	end

	hub.isOp = function( this, nick )
		if this._users[nick] and this._users[nick]:isOp() then
			return 1
		end
	end

	hub.removeUser = function( this, nick )
		this._users[nick] = nil
	end

	hub.setPartial = function( this )
		--// we're most likely missing logon info
		this._partial = 1
	end

	hub.isPartial = function( this )
		--// are we missing logon info?
		return this._partial
	end

	hub.setHubName = function( this, msg )
		this._name = msg
	end

	hub.getAddress = function( this )
		return DC():GetHubIpPort( this._id )
	end

	hub.getHubName = function( this )
		if this._name then
			return this._name.." ("..this:getAddress()..")"
		else
			return this:getAddress()
		end
	end

	hub.setOwnNick = function( this, nick )
		this._myNick = nick
	end

	hub.getOwnNick = function( this )
		if not this:isPartial() then
 			return this._myNick
 		end
		DC():PrintDebug( this:getHubName().."> Your scripts are failing. "..
					"Own nick not set. Reconnect please." )
	end

	hub.destroy = function( this )
	end

	hub.sendChat = function( this, msg )
		local ownNick = this:getOwnNick()
		if ownNick then
			DC():SendHubMessage( this:getId(), "<"..ownNick.."> "..msg.."|" )
		end
	end

	hub.injectChat = function( this, msg )
		DC():InjectHubMessage( this:getId(), msg )
	end

	hub.sendPrivMsgTo = function( this, victim, msg, hideFromSelf )
		local ownNick = this:getOwnNick()
		if ownNick then
			DC():SendHubMessage( this:getId(), "$To: "..victim.." From: "..ownNick.." $"..msg.."|" )
			if not hideFromSelf then
				this:injectPrivMsg( victim, ownNick, msg )
			end
		end
	end

	hub.injectPrivMsg = function( this, from, to, msg )
		DC():InjectHubMessage( this:getId(), "$To: "..to.." From: "..from.." $"..msg )
	end

	hub.injectPrivMsgFmt = function( this, from, to, msg )
		this:injectPrivMsg( from, to, "<"..from.."> "..msg )
	end

	hub.attention = function( this )
		DC():HubWindowAttention( this:getId() )
	end

	--////////////////
	--// Own functions
	--////////////////

	hub.onRaw = function( this, msg )
		local ret
		for k,f in dcpp:getListeners( "Raw" ) do
			ret = f( this, msg ) or ret
		end
		return ret
	end

	hub.onSearch = function( this, msg )
		--DC():PrintDebug( this:getHubName().."> "..msg )
	end

	hub.onHello = function( this, user, msg )
	end

	hub.onMyInfo = function( this, user, msg )
		local ret
		for k,f in dcpp:getListeners( "userMyInfo" ) do
			ret = f( this, user, msg, ret ) or ret
		end
		return ret
	end

	hub.onQuit = function( this, user, msg )
		local ret
		for k,f in dcpp:getListeners( "userQuit" ) do
			ret = f( this, user, ret ) or ret
		end
		return ret
	end

	hub.onHubName = function( this, hubname, msg )
	end

	hub.onPrivateMessage = function( this, user, to, prefix, text, full )
		if to == this:getOwnNick() then
			if prefix == "<"..user:getNick().."> " then
				local ret
				for k,f in dcpp:getListeners( "pm" ) do
					ret = f( this, user, text, ret ) or ret
				end
				return ret
			else
				local ret
				for k,f in dcpp:getListeners( "hubPm" ) do
					ret = f( this, user, text, ret ) or ret
				end
				return ret
			end
		end
	end

	hub.onChatMessage = function( this, user, text )
		local ret
		for k,f in dcpp:getListeners( "chat" ) do
			ret = f( this, user, text, ret ) or ret
		end
		return ret
	end

	hub.onChatFromSelf = function( this, text )
		local ret
		for k,f in dcpp:getListeners( "ownChat" ) do
			ret = f( this, text, ret ) or ret
		end
		return ret
	end

	return hub
end


--/////////////////////////////////////
--// User object
--/////////////////////////////////////

function createUser( hub, nick )
	local user = {}

	user._hub = hub
	user._nick = nick
	user._op = nil
	user._ip = ""
	user._handled_messages = {} -- flood protection

	user.setOp = function( this, op )
		this._op = op
		return this
	end

	user.isOp = function( this )
		return this._op
	end

	user.setIp = function( this, ip )
		this._ip = ip
	end

	user.getIp = function( this )
		return this._ip
	end

	user.getNick = function( this )
		return this._nick
	end

	user.sendPrivMsgFmt = function( this, msg, hideFromSelf )
		local ownNick = this._hub:getOwnNick()
		if ownNick then
			this._hub:sendPrivMsgTo( this._nick, "<"..ownNick.."> "..msg, hideFromSelf )
		end
		return this
	end

	user.setMsgHandled = function( this, which )
		this._handled_messages[which] = 1
		return this
	end

	user.msgHandled = function( this, which )
		return this._handled_messages[which]
	end

	return user
end


--/////////////////////////////////////
--// Handlers
--/////////////////////////////////////

nmdch = {}

function nmdch.DataArrival( hub, msg )
	local h = dcpp:getHub( hub )

	--// If we missed the logon, we really have no business here,
	--// modify only if you really have to.
	--// Note that if not h:isPartial and not h:getOwnNick(),
	--// functions requiring an ownNick will silently fail.
	if h:isPartial() then
		return
	end

	--// raw/unparsed message
	if h:onRaw( msg ) then
		return 1
	end

	--// parse message and fire appropriate
	local ret,c,cmd = string.find( msg, "^%$([^ ]+)" )
	if ret then
		if cmd == "Search" then
			return h:onSearch( msg )
		elseif cmd == "Hello" then
			local nick = string.sub( msg, 8 )
			if not h:getOwnNick() then
				h:setOwnNick( nick ) -- don't trust this nick on h:isPartial()
			end
			return h:onHello( h:getUser( nick ), msg )
		elseif cmd == "MyINFO" and string.sub( msg, 1, 13 ) == "$MyINFO $ALL " then
			local nick = string.sub( msg, 14, string.find( msg, " ", 14, 1 ) - 1 )
			return h:onMyInfo( h:getUser( nick ), msg )
		elseif cmd == "Quit" then
			local nick = string.sub( msg, 7 )
			h:removeUser( nick )
			return h:onQuit( nick, msg )
		elseif cmd == "HubName" then
			local hubname = string.sub( msg, 10 )
			h:setHubName( hubname )
			return h:onHubName( hubname, msg )
		elseif cmd == "OpList" then
			for nick in string.gfind( string.sub( msg, 9 ), "[^$]+") do
				h:getUser( nick, 1 )
			end
			return nil
		elseif cmd == "UserIP" then
			local nick,ip
			for combo in string.gfind( string.sub( msg, 9 ), "[^$]+") do
				ret,c,nick,ip = string.find( combo, "^(%S+) (%S+)$" )
				if ret then
					h:getUser( nick ):setIp( ip )
				end
			end
			return nil
		--elseif string.sub( msg, 1, 10 ) == "$NickList " then
		--	for nick in string.gfind( string.sub( msg, 9, -1), "[^$]+") do
		--		h:getUser( nick )
		--	end
		--	return nil
		elseif cmd == "To:" then
			local ret,c,to,from,fulltext = string.find( msg, "^%$To: ([^ ]+) From: ([^ ]+) %$(.*)$" )
			if ret then
				local ret,c,prefix,text = string.find( fulltext, "^(%b<> )(.*)$" )
				if ret then
					return h:onPrivateMessage( h:getUser( from ), to, prefix, text, msg )
				else
					return h:onPrivateMessage( h:getUser( from ), to, nil, fulltext, msg )
				end
			end
		end
	elseif string.sub( msg, 1, 1 ) == "<" then
		local ret,c,nick,text = string.find( msg, "^<([^>]+)> (.*)$" )
		if ret and h:getOwnNick() then
			if nick ~= h:getOwnNick() then -- don't be flooding mainchat now..
 				return h:onChatMessage( h:getUser( nick ), text )
			else
				return h:onChatFromSelf( text )
			end
		end
	end
end

function dcpp.UserDataIn( user, msg )
  --DC():PrintDebug( tostring( user ).." << "..msg )
end

function dcpp.UserDataOut( user, msg )
	--DC():PrintDebug( tostring( user ).." >> "..msg )
end

function dcpp.OnCommandEnter( hub, text )
	local h = dcpp:getHub( hub )
	local ret
	for k,f in dcpp:getListeners( "ownChatOut" ) do
		ret = f( h, text, ret ) or ret
	end
	return ret
end

function dcpp.OnTimer()
	-- Called every second.
	for k,f in dcpp:getListeners( "timer" ) do
		f()
	end
end

function nmdch.OnHubAdded( hub )
	dcpp:addHub( hub, 0 )
end

function nmdch.OnHubRemoved( hub )
	dcpp:removeHub( hub )
end

adch = {}

function adch.OnHubAdded( hub )
	dcpp:addHub( hub, 1 )
end

function adch.OnHubRemoved( hub )
	dcpp:removeHub( hub )
end

--/////////////////////////////////////
--// Utility functions
--/////////////////////////////////////

function SendActiveSearchResult( hub, ip_port, search_nick, filename, filesize, open_slots, total_slots )
	DC():SendUDP( ip_port, "$SR "..search_nick.." "..filename.."\005".. filesize.." "..open_slots..
			"/"..total_slots.."\005"..dcpp:getHub( hub ):getHubName() ) -- no pipe in UDP $SR
end

function _parseIter(cmd)
	local i = string.gfind(string.gsub(cmd, "\\ ", "\2"), " *([^ ]+)")
	return function()
		local j = i()
		if j == nil then return nil end
		return string.gsub(j, "\2", "\\ ")
	end
end

-- Socket handler demultiplexing.
socket = {}
socket.handlers = {}
socket.sockets = {}

function RegisterSocketServer(port, handler)
	local cookie = table.getn(socket.handlers)
	-- Listen on a given port, allowing demultiplexing to occur.
	table.insert(socket.handlers, cookie, handler)
	DC():SocketAccept(port, cookie)
end

function socket.OnAccept(s, cookie)
	-- Direct connection to appropriate handler.
	local handler = socket.handlers[cookie]
	socket.sockets[s] = handler
	handler.OnAccept(s)
end

function socket.OnConnect(s, cookie)
	DC():PrintDebug("s: " .. socket .. "; c: " .. cookie)
end

function socket.OnRead(s, data)
	-- Feed correct handler.
	socket.sockets[s].OnRead(s, data)
end

function socket.OnWrite(s)
	socket.sockets[s].OnWrite(s)
end

function socket.OnClose(s)
	-- If client delete handler.
	socket.sockets[s].OnClose(s)
	socket.sockets[s] = nil
end


--/////////////////////////////////////
--// Execute your own scripts
--/////////////////////////////////////

--dofile("Scripts/adchub.lua")
--dofile("Scripts/bier.lua")
--dofile("Scripts/httpd.lua")
--dofile("Scripts/monologue.lua")
--dofile("Scripts/onjoin.lua")
--dofile("Scripts/slots.lua")
--dofile("Scripts/uptime.lua")
